/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef NYU_HANDLE_HPP_
#define NYU_HANDLE_HPP_

#include "Value.hpp"
#include "Object.hpp"

namespace nyu {

template<typename T>
class Handle {
public:
    Handle(T* p) : p_(p) {

    }

    Handle(T* p, bool) : p_(p) {
        if (p_)
            p_->ref();
    }

    template<typename U>
    Handle(const Handle<U>& rhs) : p_(rhs.p_) {
        if (p_)
            p_->ref();
    }

    ~Handle() {
        if (p_)
            p_->unref();
    }

    template<typename U>
    Handle<T>& operator=(const Handle<U>& rhs) {
        if (p_)
            p_->unref();
        p_ = rhs.p_;
        if (p_)
            p_->ref();
        return *this;
    }

    T* operator->() const {
        return p_;
    }

    T& operator*() const {
        return *p_;
    }

    T* get() const {
        return p_;
    }

private:
    T* p_;
};

template<>
class Handle<Value> {
public:
    Handle(Value p) : p_(p) {
    }

    Handle(Value p, bool) : p_(p) {
        if (is_object(p_) && !is_null(p_))
            value_to_object(p_)->ref();
    }

    template<typename U>
    Handle(const Handle<U>& rhs) : p_(rhs.p_) {
        if (is_object(p_) && !is_null(p_))
            value_to_object(p_)->ref();
    }

    ~Handle() {
        if (is_object(p_) && !is_null(p_))
            value_to_object(p_)->unref();
    }

    template<typename U>
    Handle<Value>& operator=(const Handle<U>& rhs) {
        if (is_object(p_) && !is_null(p_))
            value_to_object(p_)->unref();
        p_ = rhs.p_;
        if (is_object(p_) && !is_null(p_))
            value_to_object(p_)->ref();
        return *this;
    }

    Object* operator->() const {
        return value_to_object(p_);
    }

    Object& operator*() const {
        return *value_to_object(p_);
    }

    Value get() const {
        return p_;
    }

private:
    Value p_;
};

template<typename T>
Handle<T> make_handle(T v) {
    return Handle<T> (v);
}

template<typename T>
inline ValueTag value_tag(Handle<T> h) {
    return value_tag(h.get());
}

template<typename T>
inline ValueTag2 value_tag(Handle<T> h1, Handle<T> h2) {
    return value_tag(h1.get(), h2.get());
}

template<typename T>
inline bool is_int(Handle<T> h) {
    return is_int(h.get());
}

template<typename T>
inline bool is_object(Handle<T> h) {
    return is_object(h.get());
}

template<typename T>
inline bool is_null(Handle<T> h) {
    return is_null(h.get());
}

template<typename T>
inline int_type value_to_int(Handle<T> h) {
    return value_to_int(h.get());
}

template<typename T>
inline Object* value_to_object(Handle<T> h) {
    return value_to_object(h.get());
}

}

#endif /* NYU_HANDLE_HPP_ */
